<?php
namespace Core;

use Core\Event as EventsManager;
use Phalcon\DI\FactoryDefault;
use Phalcon\Mvc\Application as PhalconApplication;
use Phalcon\Config as Config;
use Phalcon\Loader;
use Phalcon\Mvc\Model\MetaData\Strategy\Annotations as StrategyAnnotations;

abstract class Application extends PhalconApplication
{
    //Default module.
    const SYSTEM_DEFAULT_MODULE = 'common';

    protected $systemDefaultModule;

    protected $_bootstrap = [
        'dir',
        'loader',
        'cache',
        'module',
        'config',
        'service',
        'event'
    ];

    protected $_service = [
        'profiler',
        'environment',
        'registry',
        'logger',
        'annotations',
        'url',
        'router',
        'database',
        'session',
        'flash',
        'filter',
        'crypt',
        'api',
        'request',
        'view',
        'component'
    ];

    //Constructor.
    public function __construct() {
        parent::__construct(new FactoryDefault());
        $this->di->setShared('app', $this);
        foreach ($this->_bootstrap as $bootstrap) {
            $this->{'_init' . ucfirst($bootstrap)}();
        }
    }

    //init dirs
    protected function _initDir() {
        $this->di->setShared('dir', new Class() {
            public function root($path = []) {
                if(!is_dir(ROOT_PATH)) {
                    throw new \Exception("The application boot needs to set up the root path.");
                }
                $path = implode(DIRECTORY_SEPARATOR, $path);
                $path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
                return ROOT_PATH . DIRECTORY_SEPARATOR . $path;
            }

            public function app($path = null) {
                return $this->root(['app', $path]);
            }

            public function config($path = null) {
                return $this->root(['config', $path]);
            }

            public function library($path = null) {
                return $this->root(['library', $path]);
            }

            public function var($path = null) {
                $dir = $this->root(['var', $path]);
                if (!is_dir($dir)) {
                    @mkdir($dir, 777, true);
                }
                return $dir;
            }

        });
        return $this;
    }

    //init loader
    protected function _initLoader() {
        $namespaces = array(
            'Core' => $this->dir->library('core')
        );
        $loader = new Loader();
        $loader->registerNamespaces($namespaces);
        $loader->registerFiles([
            $this->dir->library('vendor/autoload.php'),
        ]);
        $loader->register();
        $this->di->setShared('loader', $loader);
        return $this;
    }

    protected function _initCache() {
        $configs = require_once $this->dir->config('cache.php');
        if(isset($configs['annotations'])) {
            $config = $configs['annotations'];
            unset($configs['annotations']);
            if(!$config['enabled']) {
                $config['backend'] = $config["memory"];
            }
            $this->di->setShared("annotations", new $config['backend']($config));
        }

        if(isset($configs['modelsMetadata'])) {
            $config = $configs['modelsMetadata'];
            unset($configs['modelsMetadata']);
            if(!$config['enabled']) {
                $config['backend'] = $config["memory"];
            }
            $metadata = new $config['backend']($config);
            $metadata->setStrategy(new StrategyAnnotations());
            $this->di->setShared("modelsMetadata", $metadata);
        }

        foreach ($configs as $name => $config) {
            if(!$config['enabled']) {
                $config['backend'] = $config["memory"];
            }
            $frontend = new $config['frontend']([
                'lifetime' => $config['lifetime']
            ]);
            $backend = new $config['backend']($frontend, $config);
            $this->di->setShared($name, $backend);
        };
        return $this;
    }

    //init configs
    protected function _initConfig() {
        $key = md5(__METHOD__);
        if(!($configs = $this->systemCache->start($key))) {
            //system configs
            //$files = glob("{$this->dir->config()}/*.php");
            $files = array_diff(glob($this->dir->config('*.php')), ['cache.php']);
            if ($files) {
                foreach ($files as $file) {
                    $name = strtolower(basename($file, '.php'));
                    $configs[$name] = include_once $file;
                }
            }
            //module configs
            $modules = $this->getModules();
            foreach ($modules as $moduleName => $module) {
                $dir = $this->dir->app($moduleName . '/config/');
                if (!is_dir($dir)) continue;
                $files = glob($dir . '*.php');
                if ($files){
                    foreach ($files as $file) {
                        $name = strtolower(basename($file, '.php'));
                        $configs['modules'][$moduleName][$name] = include_once $file;
                    }
                }
            }
            $this->systemCache->save($key, $configs);
        }
        $config = new Config($configs);
        if ($config->system->debug) {
            error_reporting(E_ALL ^ E_NOTICE);
            ini_set('display_errors', 1);
        }
        $this->di->setShared('config', $config);
        return $this;
    }

    //init register
    protected function _initModule() {
        $key = md5(__METHOD__);
        if(!($modules = $this->systemCache->start($key))) {
            $dirs = array_diff(scandir($this->dir->app()), ['.', '..']);
            if (!$dirs) {
                throw new \Exception('System module not found');
            }
            $modules = [];
            foreach ($dirs as $dir) {
                $name = basename($dir);
                $modules[$name] = [
                    "className" => ucfirst($name) . "\Bootstrap"
                ];
            }
            $this->systemCache->save($key, $modules);
        }
        foreach ($modules as $name => $module) {
            $namespaces[ucfirst($name)] = $this->dir->app($name);
            $namespaces[ucfirst($name) . '\Controller'] = $this->dir->app($name . '/controllers');
            $namespaces[ucfirst($name) . '\Model'] = $this->dir->app($name . '/models');
            $namespaces[ucfirst($name) . '\Command'] = $this->dir->app($name . '/commands');
        }
        $this->loader->registerNamespaces($namespaces, true);
        $this->registerModules($modules);
        return $this;
    }



    //initialize Services
    protected function _initService() {
        foreach ($this->_service as $name) {
            $class = '\Core\Service\\' . ucfirst($name);
            if (!class_exists($class)){
                continue;
            }
            $service = new $class();
            if(!method_exists($service, 'register')) {
                throw new \Exception("core service {$name} need register method.");
            }
            $this->di->remove($name);
            $service->register();
        }
        return $this;
    }

    //init event manager
    protected function _initEvent() {
        return new EventsManager();
    }

    abstract public function run();

}